#include "widget.h"
#include "ui_widget.h"
#include <QTcpSocket>
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //1，修改窗口标题.
    this->setWindowTitle("服务器");

    //2，创建 QTcpServer 的实例
    tcpServer = new QTcpServer(this);

    //3，指定如何处理连接.
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);

    //4，绑定并监听端口号，需要把前面初始化啊全完成才能开始监听端口，要开店得先装修好
    bool ret = tcpServer->listen(QHostAddress::Any, 8080); //表示愿意接收任何 ip，端口为8080
    if (!ret)
    {
        QMessageBox::critical(this, "服务器启动失败!", tcpServer->errorString()); //弹出错误对话框
        exit(1);
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::processConnection()
{
    //1，通过 tcpServer 拿到一个 socket 对象, 用来和客户端进行通信.
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection(); //每个客户端都有一个对象，所以可能有多个，所以当断开连接时必须要释放
    QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端成功连接";
    ui->listWidget->addItem(log);

    //2，处理客户端发来的请求
    connect(clientSocket, &QTcpSocket::readyRead, this, [=]() //使用 lamdba 表达式
    {
        QString request = clientSocket->readAll(); //读取请求内容，返回QByteArray，转成 QString
        const QString& response = process(request); //处理请求，构建响应，返回要发回的内容
        clientSocket->write(response.toUtf8()); //将响应发回客户端
        QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] "
                + " req: " + request + ", resp: " + response;
        ui->listWidget->addItem(log); //打印日志
        //上面的处理办法比较简陋，因为一个完整的请求可能是分成多端字节组进行传输，简单来说就是可能每一次收到的内容都不完整（粘包问题）
        //但是作为回显服务器已经足够，更好的做法是将每次收到的数据包都放到一个大的缓冲区中，并提前约定好应用层协议的格式，再进行更细致的解析
        //该步骤在主页的 http服务器 项目里已经实现过，这里就从简了
    });

    //3，处理断开连接的情况.
    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端断开连接";
        ui->listWidget->addItem(log); //打印断开连接日志
        //手动释放 clientSocket
        // delete clientSocket; //直接使用 delete 不好，有很多限制，比如要保证 delete 是最后一步，并且不能被 return 或抛异常等操作跳过，
        clientSocket->deleteLater(); //Qt 提供的，不立马销毁，告诉 Qt 在下一轮事件循环再销毁
        //槽函数都是在事件循环执行的，进入到下一轮事件循环，表示上一轮循环中要做的事已经全部做完了，也表示槽函数已经结束了
    });
}

//回显服务器.
QString Widget::process(const QString request)
{
    return request;
}

